"""
熔断器 (Circuit Breaker)

实现熔断器模式，防止级联故障，提高系统稳定性。

熔断器状态机：
CLOSED (关闭) -> OPEN (打开) -> HALF_OPEN (半开) -> CLOSED/OPEN

状态说明：
- CLOSED: 正常状态，请求正常通过
- OPEN: 熔断状态，请求直接失败，不调用后端
- HALF_OPEN: 试探状态，允许部分请求通过以测试后端是否恢复

作者: RedFire Team
创建日期: 2025-10-06
版本: v1.0
"""

from enum import Enum
from typing import Optional, Callable, Any, Dict
from datetime import datetime, timedelta
from dataclasses import dataclass, field
import asyncio
import logging
from collections import deque


class CircuitBreakerState(str, Enum):
    """熔断器状态"""
    CLOSED = "closed"           # 关闭状态（正常）
    OPEN = "open"               # 打开状态（熔断）
    HALF_OPEN = "half_open"     # 半开状态（试探）


class CircuitBreakerOpenError(Exception):
    """熔断器打开异常"""
    pass


@dataclass
class CircuitBreakerConfig:
    """
    熔断器配置
    
    参数说明：
    - failure_threshold: 失败阈值（连续失败次数）
    - success_threshold: 成功阈值（半开状态下需要的成功次数）
    - timeout: 熔断超时时间（秒），超时后进入半开状态
    - half_open_max_calls: 半开状态下允许的最大调用次数
    - failure_rate_threshold: 失败率阈值（0-1）
    - minimum_calls: 最小调用次数（达到后才计算失败率）
    - sliding_window_size: 滑动窗口大小（记录最近N次调用）
    """
    failure_threshold: int = 5
    success_threshold: int = 2
    timeout: float = 60.0
    half_open_max_calls: int = 3
    failure_rate_threshold: float = 0.5
    minimum_calls: int = 10
    sliding_window_size: int = 100


@dataclass
class CircuitBreakerMetrics:
    """熔断器指标"""
    total_calls: int = 0
    successful_calls: int = 0
    failed_calls: int = 0
    rejected_calls: int = 0
    state_transitions: int = 0
    last_state_change: Optional[datetime] = None
    last_failure: Optional[datetime] = None
    last_success: Optional[datetime] = None
    
    # 滑动窗口记录
    recent_results: deque = field(default_factory=lambda: deque(maxlen=100))
    
    def record_success(self):
        """记录成功"""
        self.total_calls += 1
        self.successful_calls += 1
        self.last_success = datetime.now()
        self.recent_results.append(True)
    
    def record_failure(self):
        """记录失败"""
        self.total_calls += 1
        self.failed_calls += 1
        self.last_failure = datetime.now()
        self.recent_results.append(False)
    
    def record_rejection(self):
        """记录拒绝"""
        self.rejected_calls += 1
    
    def record_state_change(self):
        """记录状态变更"""
        self.state_transitions += 1
        self.last_state_change = datetime.now()
    
    def get_failure_rate(self) -> float:
        """获取失败率"""
        if not self.recent_results:
            return 0.0
        
        failures = sum(1 for result in self.recent_results if not result)
        return failures / len(self.recent_results)
    
    def get_stats(self) -> Dict[str, Any]:
        """获取统计信息"""
        return {
            "total_calls": self.total_calls,
            "successful_calls": self.successful_calls,
            "failed_calls": self.failed_calls,
            "rejected_calls": self.rejected_calls,
            "state_transitions": self.state_transitions,
            "failure_rate": self.get_failure_rate(),
            "last_state_change": self.last_state_change.isoformat() if self.last_state_change else None,
            "last_failure": self.last_failure.isoformat() if self.last_failure else None,
            "last_success": self.last_success.isoformat() if self.last_success else None
        }


class CircuitBreaker:
    """
    熔断器实现
    
    使用示例：
    ```python
    # 创建熔断器
    cb_config = CircuitBreakerConfig(
        failure_threshold=5,
        timeout=60.0,
        failure_rate_threshold=0.5
    )
    circuit_breaker = CircuitBreaker("tushare", cb_config)
    
    # 使用熔断器
    try:
        result = await circuit_breaker.call(some_async_function, arg1, arg2)
    except CircuitBreakerOpenError:
        # 熔断器打开，使用降级逻辑
        result = fallback_function()
    ```
    """
    
    def __init__(
        self,
        name: str,
        config: Optional[CircuitBreakerConfig] = None,
        logger: Optional[logging.Logger] = None
    ):
        """
        初始化熔断器
        
        Args:
            name: 熔断器名称（通常是适配器名称）
            config: 熔断器配置
            logger: 日志记录器
        """
        self.name = name
        self.config = config or CircuitBreakerConfig()
        self.logger = logger or logging.getLogger(__name__)
        
        # 状态
        self._state = CircuitBreakerState.CLOSED
        self._opened_at: Optional[datetime] = None
        
        # 计数器
        self._consecutive_failures = 0
        self._consecutive_successes = 0
        self._half_open_calls = 0
        
        # 指标
        self.metrics = CircuitBreakerMetrics(
            recent_results=deque(maxlen=self.config.sliding_window_size)
        )
        
        # 锁（防止并发状态修改）
        self._lock = asyncio.Lock()
        
        self.logger.info(f"熔断器初始化: {name}, 配置={self.config}")
    
    @property
    def state(self) -> CircuitBreakerState:
        """获取当前状态"""
        return self._state
    
    @property
    def is_closed(self) -> bool:
        """是否关闭状态"""
        return self._state == CircuitBreakerState.CLOSED
    
    @property
    def is_open(self) -> bool:
        """是否打开状态"""
        return self._state == CircuitBreakerState.OPEN
    
    @property
    def is_half_open(self) -> bool:
        """是否半开状态"""
        return self._state == CircuitBreakerState.HALF_OPEN
    
    async def call(self, func: Callable, *args, **kwargs) -> Any:
        """
        通过熔断器调用函数
        
        Args:
            func: 要调用的函数（可以是同步或异步）
            *args: 位置参数
            **kwargs: 关键字参数
        
        Returns:
            Any: 函数返回值
        
        Raises:
            CircuitBreakerOpenError: 熔断器打开时抛出
        """
        async with self._lock:
            # 检查是否需要从OPEN转到HALF_OPEN
            if self.is_open:
                if self._should_attempt_reset():
                    self._transition_to_half_open()
                else:
                    self.metrics.record_rejection()
                    raise CircuitBreakerOpenError(
                        f"熔断器 {self.name} 处于打开状态"
                    )
            
            # 检查半开状态的调用次数限制
            if self.is_half_open:
                if self._half_open_calls >= self.config.half_open_max_calls:
                    self.metrics.record_rejection()
                    raise CircuitBreakerOpenError(
                        f"熔断器 {self.name} 半开状态调用次数已达上限"
                    )
                self._half_open_calls += 1
        
        # 执行调用
        try:
            # 支持异步和同步函数
            if asyncio.iscoroutinefunction(func):
                result = await func(*args, **kwargs)
            else:
                result = func(*args, **kwargs)
            
            # 成功，更新状态
            await self._on_success()
            return result
            
        except Exception as e:
            # 失败，更新状态
            await self._on_failure(e)
            raise
    
    async def _on_success(self):
        """处理成功调用"""
        async with self._lock:
            self.metrics.record_success()
            self._consecutive_failures = 0
            self._consecutive_successes += 1
            
            # 如果在半开状态，检查是否可以关闭
            if self.is_half_open:
                if self._consecutive_successes >= self.config.success_threshold:
                    self._transition_to_closed()
    
    async def _on_failure(self, error: Exception):
        """处理失败调用"""
        async with self._lock:
            self.metrics.record_failure()
            self._consecutive_successes = 0
            self._consecutive_failures += 1
            
            self.logger.warning(
                f"熔断器 {self.name} 记录失败: {error}, "
                f"连续失败={self._consecutive_failures}"
            )
            
            # 检查是否应该打开熔断器
            if self._should_open():
                self._transition_to_open()
    
    def _should_open(self) -> bool:
        """判断是否应该打开熔断器"""
        # 检查连续失败次数
        if self._consecutive_failures >= self.config.failure_threshold:
            return True
        
        # 检查失败率（需要达到最小调用次数）
        if len(self.metrics.recent_results) >= self.config.minimum_calls:
            if self.metrics.get_failure_rate() >= self.config.failure_rate_threshold:
                return True
        
        return False
    
    def _should_attempt_reset(self) -> bool:
        """判断是否应该尝试重置（从OPEN到HALF_OPEN）"""
        if not self._opened_at:
            return True
        
        elapsed = (datetime.now() - self._opened_at).total_seconds()
        return elapsed >= self.config.timeout
    
    def _transition_to_closed(self):
        """转换到关闭状态"""
        old_state = self._state
        self._state = CircuitBreakerState.CLOSED
        self._consecutive_failures = 0
        self._consecutive_successes = 0
        self._half_open_calls = 0
        self._opened_at = None
        self.metrics.record_state_change()
        
        self.logger.info(f"熔断器 {self.name} 状态变更: {old_state} -> CLOSED")
    
    def _transition_to_open(self):
        """转换到打开状态"""
        old_state = self._state
        self._state = CircuitBreakerState.OPEN
        self._opened_at = datetime.now()
        self._consecutive_successes = 0
        self._half_open_calls = 0
        self.metrics.record_state_change()
        
        self.logger.warning(
            f"熔断器 {self.name} 状态变更: {old_state} -> OPEN, "
            f"失败率={self.metrics.get_failure_rate():.2%}"
        )
    
    def _transition_to_half_open(self):
        """转换到半开状态"""
        old_state = self._state
        self._state = CircuitBreakerState.HALF_OPEN
        self._consecutive_failures = 0
        self._consecutive_successes = 0
        self._half_open_calls = 0
        self.metrics.record_state_change()
        
        self.logger.info(f"熔断器 {self.name} 状态变更: {old_state} -> HALF_OPEN")
    
    def reset(self):
        """手动重置熔断器"""
        self._state = CircuitBreakerState.CLOSED
        self._consecutive_failures = 0
        self._consecutive_successes = 0
        self._half_open_calls = 0
        self._opened_at = None
        
        self.logger.info(f"熔断器 {self.name} 已手动重置")
    
    def force_open(self):
        """手动打开熔断器"""
        self._transition_to_open()
        self.logger.warning(f"熔断器 {self.name} 已手动打开")
    
    def get_stats(self) -> Dict[str, Any]:
        """获取统计信息"""
        return {
            "name": self.name,
            "state": self._state.value,
            "consecutive_failures": self._consecutive_failures,
            "consecutive_successes": self._consecutive_successes,
            "opened_at": self._opened_at.isoformat() if self._opened_at else None,
            **self.metrics.get_stats()
        }
    
    def __repr__(self) -> str:
        return (
            f"<CircuitBreaker("
            f"name={self.name}, "
            f"state={self._state.value}, "
            f"failures={self._consecutive_failures}"
            f")>"
        )

